/* dbox.c for !ResCreate */


#include "main.h"
#include "alloc.h"
#include "dbox.h"


/* resource file identification */

#define  RESF_FILEID    'FSER'


/* built-in version numbers */

#define  RESF_VERSION    101      /* version number of resource files */
#define  WINDOW_VERSION  102      /* version number of window templates */


/* component ids */

#define  ID_OBJ_TemplateName    1
#define  ID_OBJ_Class           7
#define  ID_OBJ_Version         9
#define  ID_OBJ_BodySize      0xb
#define  ID_OBJ_Strings      0x1a
#define  ID_OBJ_Messages     0x1b
#define  ID_OBJ_SpriteRefs   0x1c
#define  ID_OBJ_Offsets      0x1d

#define  ID_GADG_TemplateName   1
#define  ID_GADG_Type           5
#define  ID_GADG_BodySize     0xb
#define  ID_GADG_Strings      0xf
#define  ID_GADG_Messages    0x19
#define  ID_GADG_SpriteRefs  0x1a
#define  ID_GADG_AtBack      0x1b


/* identification lines for object and gadget definitions in text form */

#define  OBJ_HDR       "Object class definition file"
#define  OBJ_VERSION   "Version 100"

#define  GADG_HDR      "Gadget type definition file"
#define  GADG_VERSION  "Version 100"

#define  DEF_END       "END"


/* built-in limits! */

#define  MAX_BODY_SIZE   1024    /* max size for object or gadget body */
#define  FIELD_BUFF_SIZE  256    /* max length of offsets list in dbox */
#define  MAX_FIELDS  (MAX_BODY_SIZE >> 2)   /* fields are 4 bytes each */

/*
 * This array is used to check for duplicate field definitions.
 * It is cleared by check_body_size(..), and check_offset_list(..) sets its
 *  i'th element to 1 for each offset found.
 */

static char used [MAX_FIELDS];

static char *buff = NULL;    /* addresses the current SaveAs buffer (or 0) */

static char currfield [FIELD_BUFF_SIZE];
                             /* holds current dbox field (see getval(..))  */


/* the *real* size of a WindowTemplate */

#define  SIZEOF_WINDOWTEMPLATE  (sizeof (WindowTemplate))


/* buffer for dbox_load_from_file (..) */

static char line [256];



/*
 * Reads an integer from a buffer:
 *
 *   optional & at start indicates hexadecimal value
 *   must have at least one digit
 *   non-digit terminates
 *
 * Result is terminating character (which will be the same as the starting
 *  character if just an ampersand is present)
 */

static char * get_int (char *buff, unsigned *n)
{
    Bool hex = (*buff == '&');

    *n = 0;

    if (hex)
    {
        if (!isxdigit (buff[1]))
            return buff;

        buff++;
        while (isxdigit (*buff))
        {
            switch (*buff)
            {
            case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
                *n  = *n * 16 + (*buff - 'A' + 10);
                break;
            case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
                *n  = *n * 16 + (*buff - 'a' + 10);
                break;
            case '0': case '1': case '2': case '3': case '4':
            case '5': case '6': case '7': case '8': case '9':
                *n  = *n * 16 + (*buff - '0');
                break;
            }
            buff++;
        }
    }
    else
    {
        while (isdigit (*buff))
        {
            *n  = *n * 10 + (*buff - '0');
            buff++;
        }
    }

    return buff;
}


/*
 * Reads the value from the given field of the dialogue box into the
 *  buffer 'currfield'.
 */

static void get_val (ObjectId dbox, ComponentId id)
{
    EE (writablefield_get_value(0, dbox, id, currfield, FIELD_BUFF_SIZE, 0));

    return;
}


/*
 * Each of the following functions reads field 'id' from dialogue 'dbox' and
 *  checks that it is valid.
 *
 * If all is well, the variable 'len' is increased by the length of the field
 *  (allowing for a terminating '\n') and TRUE is returned; if not, an error
 *  is reported and FALSE returned.
 */

static Bool check_template_name (ObjectId dbox, ComponentId id, int *len)
{
    get_val (dbox, id);
    if (*currfield == 0)
    {
        error_box (error_lookup ("NoName"));
        return FALSE;
    }

    *len += strlen (currfield) + 1;
    return TRUE;
}


static Bool check_class (ObjectId dbox, ComponentId id, int *len)
{
    char *s;
    unsigned n;

    get_val (dbox, id);
    if (*currfield == 0)
    {
        error_box (error_lookup ("NoClass"));
        return FALSE;
    }

    s = get_int (currfield, &n);
    if (*s != 0)
    {
        error_box (error_lookup ("BadClass"));
        return FALSE;
    }

    *len += strlen (currfield) + 1;
    return TRUE;
}


static Bool check_type (ObjectId dbox, ComponentId id, int *len)
{
    char *s;
    unsigned n;

    get_val (dbox, id);
    if (*currfield == 0)
    {
        error_box (error_lookup ("NoType"));
        return FALSE;
    }

    s = get_int (currfield, &n);
    if (*s != 0)
    {
        error_box (error_lookup ("BadType"));
        return FALSE;
    }

    if ((n & 0xffff0000) != 0)
    {
        error_box (error_lookup ("TypeRange"));
        return FALSE;
    }

    *len += strlen (currfield) + 1;
    return TRUE;
}


static Bool check_version (ObjectId dbox, ComponentId id, int *len)
{
    char *s;
    unsigned n;

    get_val (dbox, id);
    if (*currfield == 0)
    {
        error_box (error_lookup ("NoVersion"));
        return FALSE;
    }

    s = get_int (currfield, &n);
    if (*s != 0)
    {
        error_box (error_lookup ("BadVersion"));
        return FALSE;
    }

    if (n > 9999)
    {
        error_box (error_lookup ("VersionRange"));
        return FALSE;
    }

    *len += strlen (currfield) + 1;
    return TRUE;
}


/*
 * This array is used to check for duplicate field definitions.
 * It is cleared by check_body_size(..), and check_offset_list(..) sets its
 *  i'th element to 1 for each offset found.
 */

static char used [MAX_FIELDS];


static Bool check_body_size
(
    ObjectId dbox,
    ComponentId id,
    int *len,
    int *bodysize       /* set to the bodysize value */
)
{
    char *s;
    unsigned n;

    get_val (dbox, id);
    if (*currfield == 0)
    {
        error_box (error_lookup ("NoSize"));
        return FALSE;
    }

    s = get_int (currfield, &n);
    if (*s != 0)
    {
        error_box (error_lookup ("BadSize"));
        return FALSE;
    }

    if (n > MAX_BODY_SIZE)
    {
        error_box (error_lookup ("SizeRange"));
        return FALSE;
    }

    if ((n & 3) != 0)
    {
        error_box (error_lookup ("SizeAlign"));
        return FALSE;
    }

    *len += strlen (currfield) + 1;
    *bodysize = n;

    /* clear 'duplicates' array */
    memset (used, 0, MAX_FIELDS);

    return TRUE;
}


static Bool check_offset_list
(
    ObjectId dbox,
    ComponentId id,
    int *len,
    int *nrelocs,      /* increased by number of entries in list */
    char *kind,        /* eg "string reference" (for errors) */
    int bodysize       /* set to bodysize (for checking offsets) */
)
{
    char *s;
    unsigned n;

    get_val (dbox, id);

    s = currfield;
    while (*s != 0)
    {
        s = get_int (s, &n);
        if (*s != 0 && *s != ',')
        {
            error_box (error_lookup ("BadList", kind));
            return FALSE;
        }
        if (*s != 0)
        {
            s++;
            if (*s == 0 || *s == ',')
            {
                error_box (error_lookup ("BadList", kind));
                return FALSE;
            }
        }

        if (n >= bodysize)
        {
            error_box (error_lookup ("OffsetRange", kind));
            return FALSE;
        }

        if ((n & 3) != 0)
        {
            error_box (error_lookup ("OffsetAlign", kind));
            return FALSE;
        }

        if (used [n >> 2] != 0)
        {
            error_box (error_lookup ("DupOffset", n));
            return FALSE;
        }
        used [n >> 2] = 1;

        (*nrelocs)++;
    }

    *len += strlen (currfield) + 1;
    return TRUE;
}


/*
 * Checks that 'dbox' contains a valid definition of an object.
 *
 * If so, the result is TRUE; otherwise an error is reported and the result
 *  is FALSE.
 *
 * If successful:
 *   'filesize' is set to the size of the resource file required
 *   'deflen' is set to the size of the text file required
 */

static Bool check_object_def (ObjectId dbox, int *filesize, int *deflen)
{
    int bodysize;
    int nrelocs;
    int dummy;

    if (filesize == NULL)
        filesize = &dummy;
    if (deflen == NULL)
        deflen = &dummy;

    nrelocs = 0;
    *deflen = strlen (OBJ_HDR"\n"OBJ_VERSION"\n"DEF_END"\n");

    if (check_template_name (dbox, ID_OBJ_TemplateName, deflen) &&
        check_class (dbox, ID_OBJ_Class, deflen) &&
        check_version (dbox, ID_OBJ_Version, deflen) &&
        check_body_size (dbox, ID_OBJ_BodySize, deflen, &bodysize) &&
        check_offset_list (dbox, ID_OBJ_Strings, deflen, &nrelocs,
                                 "string reference", bodysize) &&
        check_offset_list (dbox, ID_OBJ_Messages, deflen, &nrelocs,
                                 "message reference", bodysize) &&
        check_offset_list (dbox, ID_OBJ_SpriteRefs, deflen, &nrelocs,
                                 "sprite area reference", bodysize) &&
        check_offset_list (dbox, ID_OBJ_Offsets, deflen, &nrelocs,
                                 "object offset", bodysize))
    {
        *filesize = bodysize +
                    sizeof (ResourceFileHeader) +
                    sizeof (ResourceFileObjectTemplateHeader) +
                    (nrelocs == 0 ? 0 :
                        offsetof (RelocationTable, relocations) +
                        nrelocs * sizeof (Relocation));

        return TRUE;
    }
    else
        return FALSE;
}


/*
 * Checks that 'dbox' contains a valid definition of a gadget.
 *
 * If so, the result is TRUE; otherwise an error is reported and the result
 *  is FALSE.
 *
 * If successful:
 *   'filesize' is set to the size of the resource file required
 *   'deflen' is set to the size of the text file required
 */

static Bool check_gadget_def (ObjectId dbox, int *filesize, int *deflen)
{
    int bodysize;
    int nrelocs;
    int dummy;

    if (filesize == NULL)
        filesize = &dummy;
    if (deflen == NULL)
        deflen = &dummy;

    nrelocs = 13;  /* help_message, pointer_shape, menu, keyboard_shortcuts,
                      gadgets,
                      toolbar_ibl, toolbar_itl, toolbar_ebl, toolbar_etl,
                      sprite_area, title_text, title_validation,
                      and help_message for the gadget */
    *deflen = strlen (GADG_HDR"\n"GADG_VERSION"\n"DEF_END"\n");
    *deflen += 2;   /* for the "At back" flag */

    if (check_template_name (dbox, ID_GADG_TemplateName, deflen) &&
        check_type (dbox, ID_GADG_Type, deflen) &&
        check_body_size (dbox, ID_GADG_BodySize, deflen, &bodysize) &&
        check_offset_list (dbox, ID_GADG_Strings, deflen, &nrelocs,
                                 "string reference", bodysize) &&
        check_offset_list (dbox, ID_GADG_Messages, deflen, &nrelocs,
                                 "message reference", bodysize) &&
        check_offset_list (dbox, ID_GADG_SpriteRefs, deflen, &nrelocs,
                                 "sprite area reference", bodysize))
    {
        unsigned type;
        int windtitlelen;

        /* determine size of window title */
        get_val (dbox, ID_GADG_Type);
        get_int (currfield, &type);
        sprintf (currfield, message_lookup ("GadgWindTitle"), type);
        windtitlelen = ((strlen (currfield) + 1) + 3) & ~3;  /* round up */

        *filesize = bodysize +
                    sizeof (ResourceFileHeader) +
                    sizeof (ResourceFileObjectTemplateHeader) +
                    SIZEOF_WINDOWTEMPLATE +
                    sizeof (GadgetHeader) +
                    windtitlelen +             /* Messages table size */
                    offsetof (RelocationTable, relocations) +
                    nrelocs * sizeof (Relocation);

        return TRUE;
    }
    else
        return FALSE;
}


/*
 * Copies a field to the output buffer for a text file, returning the address
 *  of the next free byte.
 */

static char * add_text_field (ObjectId dbox, ComponentId id, char *buff)
{
    get_val (dbox, id);
    strcpy (buff, currfield);
    buff += strlen (currfield);
    *buff++ = '\n';

    return buff;
}


/*
 * Returns the string value of field 'id' in 'dbox'
 */

static char * read_string (ObjectId dbox, ComponentId id)
{
    get_val (dbox, id);
    return currfield;
}


/*
 * Returns the integer value of field 'id' in 'dbox'
 */

static int read_int (ObjectId dbox, ComponentId id)
{
    unsigned n;

    get_val (dbox, id);
    get_int (currfield, &n);

    return n;
}


/*
 * Processes the list of offsets in field 'id' of 'dbox'.
 *
 * Each offset identifies a reference field of type 'reftype' in the body of
 *  an object or gadget (according to 'isgadget') whose address is 'body'.
 *
 * The field is initialised to NULL (-1), and an appropriate relocation table
 *  entry is added at 'reloc', which is updated; 'nrelocs' is incremented.
 */

static void process_ref_list
(
    ObjectId dbox,
    ComponentId id,
    int reftype,
    char *body,
    Relocation **reloc,
    int *nrelocs,
    Bool isgadget
)
{
    unsigned offset;
    char * s;

    /* retrieve the field value from the dbox */
    get_val (dbox, id);

    /* process each offset from the list in turn */
    s = currfield;
    while (*s != 0)
    {
        s = get_int (s, &offset);

        /* initialise reference field */
        *((int *) (body + offset)) = -1;

        /* create the relocation table entry */
        if (isgadget)
            offset += (SIZEOF_WINDOWTEMPLATE + sizeof (GadgetHeader));
        (*reloc)->word_to_relocate = offset;
        (*reloc)->directive = reftype;

        /* update relocation table pointer and count */
        (*reloc)++;
        (*nrelocs)++;

        if (*s == ',')
            s++;
    }

    return;
}


/*
 * Builds a resource file in 'buff' containing the object defined by 'dbox';
 *  returns the address of the next free byte in the buffer.
 */

static char * build_res_object_def (ObjectId dbox, char *buff)
{
    ResourceFileHeader *reshdr = (ResourceFileHeader *) buff;
    ResourceFileObjectTemplateHeader *resobjhdr =
        (ResourceFileObjectTemplateHeader *) (reshdr + 1);
    ObjectTemplateHeader *hdr = &resobjhdr->hdr;
    char *body = (char *) (resobjhdr + 1);
    int bodysize;
    RelocationTable *reloctable;
    Relocation *reloc;
    int nrelocs;

    /* retrieve body size */
    bodysize = read_int (dbox, ID_OBJ_BodySize);

    /* initialise relocation table pointers */
    reloctable = (RelocationTable *) (body + bodysize);
    reloc = reloctable->relocations;
    nrelocs = 0;

    /* fill in resource file header */
    reshdr->file_id = RESF_FILEID;
    reshdr->version_number = RESF_VERSION;
    reshdr->object_offset = (char *) resobjhdr - (char *) reshdr;

    /* fill in object header */
    hdr->object_class = read_int (dbox, ID_OBJ_Class);
    hdr->flags = 0;
    hdr->version = read_int (dbox, ID_OBJ_Version);
    strcpy (hdr->name, read_string (dbox, ID_OBJ_TemplateName));
    hdr->total_size = bodysize +                       /* no strings or */
                      sizeof (ObjectTemplateHeader);   /* messages      */
    hdr->body = (void *) sizeof (ObjectTemplateHeader);
    hdr->body_size = bodysize;

    /* clear object body */
    memset (body, 0, bodysize);

    /* initialise each reference field, adding a relocation entry */
    process_ref_list (dbox, ID_OBJ_Strings, Relocate_StringReference, body,
                                            &reloc, &nrelocs, FALSE);
    process_ref_list (dbox, ID_OBJ_Messages, Relocate_MsgReference, body,
                                            &reloc, &nrelocs, FALSE);
    process_ref_list (dbox, ID_OBJ_SpriteRefs, Relocate_SpriteAreaReference,
                                            body,
                                            &reloc, &nrelocs, FALSE);
    process_ref_list (dbox, ID_OBJ_Offsets, Relocate_ObjectOffset, body,
                                            &reloc, &nrelocs, FALSE);

    /* record number of relocations */
    if (nrelocs != 0)
        reloctable->num_relocations = nrelocs;

    /* fill in table pointers */
    resobjhdr->string_table_offset = -1;     /* no strings */
    resobjhdr->message_table_offset = -1;    /* no messages */
    resobjhdr->relocation_table_offset =
        (nrelocs == 0) ? -1 :
              sizeof (ResourceFileObjectTemplateHeader) + bodysize;

    /* return address of first byte beyond template */
    return (nrelocs == 0) ? (char *) reloctable : (char *) reloc;
}


/*
 * Builds a text file in 'buff' containing the object definition in 'dbox';
 *  returns the address of the next free byte in the buffer.
 */

static char * build_text_object_def (ObjectId dbox, char *buff)
{
    strcpy (buff, OBJ_HDR"\n"OBJ_VERSION"\n");
    buff += strlen (OBJ_HDR"\n"OBJ_VERSION"\n");

    buff = add_text_field (dbox, ID_OBJ_TemplateName, buff);
    buff = add_text_field (dbox, ID_OBJ_Class, buff);
    buff = add_text_field (dbox, ID_OBJ_Version, buff);
    buff = add_text_field (dbox, ID_OBJ_BodySize, buff);
    buff = add_text_field (dbox, ID_OBJ_Strings, buff);
    buff = add_text_field (dbox, ID_OBJ_Messages, buff);
    buff = add_text_field (dbox, ID_OBJ_SpriteRefs, buff);
    buff = add_text_field (dbox, ID_OBJ_Offsets, buff);

    strcpy (buff, DEF_END"\n");
    buff += strlen (DEF_END"\n");

    return buff;
}


/*
 * The field at 'field' is a reference of type 'reftype'; it is initialised
 *  to NULL (-1) and a suitable relocation added to the relocation table.
 *
 * 'base' addresses the object's body and 'reloc' identifies the next
 *  available relocation table entry; 'reloc' and 'nrelocs' are updated,
 */

static void add_ref
(
    int reftype,
    char **field,
    char *base,
    Relocation **reloc,
    int *nrelocs
)
{
    /* initialise reference field */
    *((int *) (field)) = -1;

    /* create the relocation table entry */
    (*reloc)->word_to_relocate = (char *) field - base;
    (*reloc)->directive = reftype;

    /* update relocation table pointer and count */
    (*reloc)++;
    (*nrelocs)++;

    return;
}


/*
 * Builds a resource file in 'buff' containing the gadget defined by 'dbox';
 *  returns the address of the next free byte in the buffer.
 */

static char * build_res_gadget_def (ObjectId dbox, char *buff)
{
    ResourceFileHeader *reshdr = (ResourceFileHeader *) buff;
    ResourceFileObjectTemplateHeader *resobjhdr =
        (ResourceFileObjectTemplateHeader *) (reshdr + 1);
    ObjectTemplateHeader *objhdr = &resobjhdr->hdr;
    WindowTemplate *window = (WindowTemplate *) (resobjhdr + 1);
    WimpWindow *w = &window->window;
    GadgetHeader *hdr = (GadgetHeader *)
                              ((char *) window + SIZEOF_WINDOWTEMPLATE);
    char *body = (char *) (hdr + 1);
    int bodysize;
    char *messagetable;
    int messagetablesize;
    RelocationTable *reloctable;
    Relocation *reloc;
    int nrelocs;
    Bool backflag = FALSE;

    /* retrieve body size */
    bodysize = read_int (dbox, ID_GADG_BodySize);

    /* initialise message table pointer */
    messagetable = body + bodysize;

    /* fill in message table (just the window title) and note its size */
    sprintf (messagetable,
             message_lookup ("GadgWindTitle"),
             read_int (dbox, ID_GADG_Type));
    messagetablesize = ((strlen (messagetable) + 1) + 3) & ~3; /* round up */

    /* initialise relocation table pointers */
    reloctable = (RelocationTable *) (messagetable + messagetablesize);
    reloc = reloctable->relocations;
    nrelocs = 0;

    /* fill in resource file header */
    reshdr->file_id = RESF_FILEID;
    reshdr->version_number = RESF_VERSION;
    reshdr->object_offset = (char *) resobjhdr - (char *) reshdr;

    /* fill in object header */
    objhdr->object_class = Window_ObjectClass;
    objhdr->flags = 0;
    objhdr->version = WINDOW_VERSION;
    strcpy (objhdr->name, read_string (dbox, ID_GADG_TemplateName));
    objhdr->total_size = SIZEOF_WINDOWTEMPLATE +
                         sizeof (GadgetHeader) +
                         bodysize +
                         messagetablesize +               /* no strings */
                         sizeof (ObjectTemplateHeader);
    objhdr->body = (void *) sizeof (ObjectTemplateHeader);
    objhdr->body_size =  SIZEOF_WINDOWTEMPLATE +
                         sizeof (GadgetHeader) +
                         bodysize;

    /* clear window template body - including gadget */
    memset ((char *) window, 0, SIZEOF_WINDOWTEMPLATE +
                                  sizeof (GadgetHeader) + bodysize);

    /* initialise other fields in the window template */
    add_ref (Relocate_MsgReference, &window->help_message,
                 (char *) window, &reloc, &nrelocs);
    add_ref (Relocate_StringReference, &window->pointer_shape,
                 (char *) window, &reloc, &nrelocs);
    add_ref (Relocate_StringReference, &window->menu,
                 (char *) window, &reloc, &nrelocs);
    add_ref (Relocate_ObjectOffset, (char **) &window->keyboard_shortcuts,
                 (char *) window, &reloc, &nrelocs);
    add_ref (Relocate_ObjectOffset, (char **) &window->gadgets,
                 (char *) window, &reloc, &nrelocs);
    window->num_gadgets = 1;
    window->gadgets = (Gadget *) (SIZEOF_WINDOWTEMPLATE);
    window->default_focus = -1;
    add_ref (Relocate_StringReference, &window->toolbar_ibl,
                 (char *) window, &reloc, &nrelocs);
    add_ref (Relocate_StringReference, &window->toolbar_itl,
                 (char *) window, &reloc, &nrelocs);
    add_ref (Relocate_StringReference, &window->toolbar_ebl,
                 (char *) window, &reloc, &nrelocs);
    add_ref (Relocate_StringReference, &window->toolbar_etl,
                 (char *) window, &reloc, &nrelocs);

    /* initialise other fields in the wimp window template */
    w->visible_area.xmin = 200;
    w->visible_area.ymin = 200;
    w->visible_area.xmax = 800;
    w->visible_area.ymax = 500;
    w->extent.xmin = 0;
    w->extent.ymin = -300;
    w->extent.xmax = 600;
    w->extent.ymax = 0;
    w->behind = WimpWindow_Top;
    w->flags = WimpWindow_Moveable |
               WimpWindow_AutoRedraw |
               WimpWindow_BackIcon |
               WimpWindow_CloseIcon |
               WimpWindow_TitleIcon |
               WimpWindow_NewFormat;
    w->title_fg = 7;
    w->title_bg = 2;
    w->work_fg = 7;
    w->work_bg = 1;
    w->scroll_outer = 3;
    w->scroll_inner = 1;
    w->highlight_bg = 12;
    w->title_flags = WimpIcon_Text | WimpIcon_HCentred | WimpIcon_Indirected;
    w->work_flags = 0;
    add_ref (Relocate_SpriteAreaReference, (char **) &w->sprite_area,
                 (char *) window, &reloc, &nrelocs);
    add_ref (Relocate_MsgReference, &w->title_data.it.buffer,
                 (char *) window, &reloc, &nrelocs);
    add_ref (Relocate_StringReference, &w->title_data.it.validation,
                 (char *) window, &reloc, &nrelocs);
    w->title_data.it.buffer = (char *) 0;    /* first (and only) entry in
                                                messages table */
    w->title_data.it.buffer_size  = messagetablesize;   /* large enough */
    w->nicons = 0;

    /* initialise gadget header */
    optionbutton_get_state (0, dbox, ID_GADG_AtBack, &backflag);
    hdr->flags = backflag ? Gadget_AtBack : 0;
    hdr->type = read_int (dbox, ID_GADG_Type) +
                         ((bodysize + sizeof (GadgetHeader)) << 16);
    hdr->box.xmin = 250;
    hdr->box.xmax = 350;
    hdr->box.ymin = -200;
    hdr->box.ymax = -100;
    hdr->component_id = 1;
    add_ref (Relocate_MsgReference, &hdr->help_message,
                 (char *) window, &reloc, &nrelocs);

    /* initialise reference fields in the gadget's body */
    process_ref_list (dbox, ID_GADG_Strings, Relocate_StringReference, body,
                                             &reloc, &nrelocs, TRUE);
    process_ref_list (dbox, ID_GADG_Messages, Relocate_MsgReference, body,
                                             &reloc, &nrelocs, TRUE);
    process_ref_list (dbox, ID_GADG_SpriteRefs, Relocate_SpriteAreaReference,
                                             body,
                                             &reloc, &nrelocs, TRUE);

    /* record number of relocations */
    reloctable->num_relocations = nrelocs;

    /* fill in table pointers */
    resobjhdr->string_table_offset = -1;     /* no strings */
    resobjhdr->message_table_offset = 
                                (char *) messagetable - (char *) resobjhdr;
    resobjhdr->relocation_table_offset =
                                (char *) reloctable - (char *) resobjhdr;

    /* return address of first byte beyond template */
    return (char *) reloc;
}


/*
 * Builds a text file in 'buff' containing the gadget definition in 'dbox';
 *  returns the address of the next free byte in the buffer.
 */

static char * build_text_gadget_def (ObjectId dbox, char *buff)
{
    strcpy (buff, GADG_HDR"\n"GADG_VERSION"\n");
    buff += strlen (GADG_HDR"\n"GADG_VERSION"\n");

    buff = add_text_field (dbox, ID_GADG_TemplateName, buff);
    buff = add_text_field (dbox, ID_GADG_Type, buff);
    {
        Bool flag;

        optionbutton_get_state (0, dbox, ID_GADG_AtBack, &flag);
        *buff++ = flag ? '1' : '0';
        *buff++ = '\n';
    }
    buff = add_text_field (dbox, ID_GADG_BodySize, buff);
    buff = add_text_field (dbox, ID_GADG_Strings, buff);
    buff = add_text_field (dbox, ID_GADG_Messages, buff);
    buff = add_text_field (dbox, ID_GADG_SpriteRefs, buff);

    strcpy (buff, DEF_END"\n");
    buff += strlen (DEF_END"\n");

    return buff;
}


/*
 * Constructs a buffer containing a resource file containing the object or
 *  gadget (according to 'isobject') defined by 'dbox'.
 *
 * If successful, the address of the buffer is returned as result, and its
 *  length is set in 'size'.
 *
 * If not, an error message is displayed and the result is NULL.
 */

char * dbox_save (ObjectId dbox, Bool isobject, int *size)
{
    Bool (* check_def) (ObjectId, int *, int *);
    char * (* build_def) (ObjectId, char *);

    check_def = (isobject ? check_object_def : check_gadget_def);
    build_def = (isobject ? build_res_object_def : build_res_gadget_def);

    if (check_def (dbox, size, NULL))  /* check and size definition */
    {
        /* free previous buffer, if any */
        free (buff);
        buff = 0;

        /* allocate new buffer if possible */
        EDG (ret, check_alloc ((void **) &buff, *size));

        /* construct the definition in the buffer */
        {
            int nused = build_def (dbox, buff) - buff;

            /* consistency check */
            if (nused != *size)
                error_exit (error_lookup ("SizeProb"));
        }

        return buff;
    }

  ret:
    return NULL;
}


/*
 * Constructs a buffer containing a text file representing the definition of
 *  the object or gadget (according to 'isobject') defined by 'dbox'.
 *
 * If successful, the address of the buffer is returned as result, and its
 *  length is set in 'size'.
 *
 * If not, an error message is displayed and the result is NULL.
 */

char * dbox_save_as_text (ObjectId dbox, Bool isobject, int *size)
{
    Bool (* check_def) (ObjectId, int *, int *);
    char * (* build_def) (ObjectId, char *);

    check_def = (isobject ? check_object_def : check_gadget_def);
    build_def = (isobject ? build_text_object_def : build_text_gadget_def);

    if (check_def (dbox, NULL, size))  /* check and size definition */
    {
        /* free previous buffer, if any */
        free (buff);
        buff = 0;

        /* allocate new buffer if possible */
        EDG (ret, check_alloc ((void **) &buff, *size + 1));
                                                    /* for zero terminator */

        /* construct the definition in the buffer */
        {
            int nused = build_def (dbox, buff) - buff;

            /* consistency check */
            if (nused != *size)
                error_exit (error_lookup ("SizeProb"));
        }

        return buff;
    }

  ret:
    return NULL;
}


/*
 * Fill in a field from a line in a definition file
 */

static void file_to_dbox (ObjectId dbox, ComponentId id, FILE *f)
{
    if (fgets (line, 256, f))
    {
        /* remove terminating newline character */
        line [strlen (line) - 1] = 0;

        ED (writablefield_set_value (0, dbox, id, line));
    }

    return;
}


/*
 * Loads an object definition from open file 'f' into 'dbox'.
 *
 * Returns 'dbox' if successful, otherwise 0. 
 */

static ObjectId load_object_def (ObjectId dbox, FILE *f)
{
    /* check version number */
    if (!fgets (line, 256, f))
        return 0;
    if (strcmp (line, OBJ_VERSION"\n"))
        return 0;

    file_to_dbox (dbox, ID_OBJ_TemplateName, f);
    file_to_dbox (dbox, ID_OBJ_Class, f);
    file_to_dbox (dbox, ID_OBJ_Version, f);
    file_to_dbox (dbox, ID_OBJ_BodySize, f);
    file_to_dbox (dbox, ID_OBJ_Strings, f);
    file_to_dbox (dbox, ID_OBJ_Messages, f);
    file_to_dbox (dbox, ID_OBJ_SpriteRefs, f);
    file_to_dbox (dbox, ID_OBJ_Offsets, f);

    /* check terminating line */
    if (!fgets (line, 256, f))
        error_box (error_lookup ("NoTerm"));
    else if (strcmp (line, DEF_END"\n"))
        error_box (error_lookup ("BadTerm"));

    return dbox;
}


/*
 * Loads a gadget definition from open file 'f' into 'dbox'.
 *
 * Returns 'dbox' if successful, otherwise 0. 
 */

static ObjectId load_gadget_def (ObjectId dbox, FILE *f)
{
    /* check version number */
    if (!fgets (line, 256, f))
        return 0;
    if (strcmp (line, GADG_VERSION"\n"))
        return 0;

    file_to_dbox (dbox, ID_GADG_TemplateName, f);
    file_to_dbox (dbox, ID_GADG_Type, f);
    if (fgets (line, 256, f))
        optionbutton_set_state (0, dbox, ID_GADG_AtBack, line[0] == '1');
    file_to_dbox (dbox, ID_GADG_BodySize, f);
    file_to_dbox (dbox, ID_GADG_Strings, f);
    file_to_dbox (dbox, ID_GADG_Messages, f);
    file_to_dbox (dbox, ID_GADG_SpriteRefs, f);

    /* check terminating line */
    if (!fgets (line, 256, f))
        error_box (error_lookup ("NoTerm"));
    else if (strcmp (line, DEF_END"\n"))
        error_box (error_lookup ("BadTerm"));

    return dbox;
}


/*
 * The file 'fname' is expected to contain a gadget or object definition in
 *  text form. If possible, this is loaded into 'objdbox' or 'gadgdbox' as
 *  appropriate.
 *
 * The result is the id of the dbox that has been updated, or 0 if neither.
 */

ObjectId dbox_load_from_file
(
    char *fname,
    ObjectId objdbox,
    ObjectId gadgdbox
)
{
    FILE *f = fopen (fname, "r");
    Bool isobject;
    ObjectId dbox = 0;

    if (!f)
        return 0;

    /* identify kind of definition */
    if (!fgets (line, 256, f))
        goto ret;

    if (strcmp (line, OBJ_HDR"\n") == 0)
        isobject = TRUE;
    else if (strcmp (line, GADG_HDR"\n") == 0)
        isobject = FALSE;
    else
        goto ret;

    /* load appropriate dbox */
    if (isobject)
        dbox = load_object_def (objdbox, f);
    else
        dbox = load_gadget_def (gadgdbox, f);

  ret:
    fclose (f);
    return dbox;
}
